(ns clj-8583.response
  (:use [clj-8583.binary]
        [clj-8583.business]))

(defn- error [field-name error-message data]
  {:errors [(str "(" (name field-name) ") Error: " error-message ". The data: [" data "]")]})

(defn- no-errors? [potential]
  (empty? (:errors potential)))

(defn- fixed-field
  [length]
  {:reader
    (fn [input]
      (if (>= (count input) length)
        (let [vval (clojure.string/trim (subs input 0 length))
              remaning-input (subs input length)]
          [vval remaning-input])
        [(error "field-name" (str "Less than " length " bytes available") input)]))
  :writer
    (fn [value]
      (str value
        (if-not (= (count value) length) (format (str "%" (- length (count value)) "s") ""))))})


(defn- variable-field [length-of-length]
  {:reader
    (fn [input]
      (let [length (Integer/parseInt (subs input 0 length-of-length))
            vval (clojure.string/trim (subs input length-of-length (+ length-of-length length)))
            remaning-input (subs input (+ length-of-length length))]
        [vval remaning-input]))
   :writer
     (fn [value]
        (str (format (str "%0" length-of-length "d") (count value)) value))
  })

(defn addi-codec
  [name
   {reader :reader writer :writer}]
  {:name name
   :reader (partial reader)
   :writer (partial writer)})

(defn make-addi-codecs
  [definitions]
  (let [make-add-codec #(apply addi-codec %)]
    (into [] (map make-add-codec definitions))))

(def addi-definitions
  (make-addi-codecs
    [[:use-type (fixed-field 2)]
    [:user-type (fixed-field 2)]
    [:user-code (fixed-field 40)]
    [:area-code (fixed-field 4)]
    [:area-add-code (fixed-field 4)]
    [:month (fixed-field 6)]
    [:data (variable-field 3)]
    [:end-flag (fixed-field 1)]]))

(defn parse-additional
  [addi-str]
  (loop [remaning addi-str
         addi-index 0
         fields {}]
    (if (> (count remaning) 0)
      (let [addi-def (nth addi-definitions addi-index)
            [vval remaning] ((:reader addi-def) remaning)]
        (recur remaning
               (+ addi-index 1)
               (if (no-errors? vval)
                  (assoc fields (:name addi-def) vval)
                  (merge-with concat fields vval))))
      fields)))

(defn write-additional
  [fields]
  (apply str (map #((:writer %) ((:name %) fields)) addi-definitions)))


(defmulti business-behavior (fn [fields addi-fields] (subs (:data addi-fields) 0 2)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defmethod business-behavior "01"
  [fields addi-fields]
  (let [b-type (keyword (subs (:data addi-fields) 0 2))
        m-type (keyword (subs (:data addi-fields) 2))
        mdef business-message-definitions
        send-func (-> mdef :send-func)
        send-data (-> mdef :send-data)
        send-format (-> mdef b-type m-type :req)
        get-data-func (-> mdef b-type m-type :send-data)
        recv-data (send-func (apply str (send-data (get-data-func fields) send-format)))
        recv-format (first (-> business-message-definitions b-type m-type :res))
        ret-code (subs recv-data (:pos recv-format) (+ (:pos recv-format) (:length recv-format)))]
    (prn (apply str (send-data (get-data-func fields) send-format)))
    (prn "haha recv-data = " recv-data)
    (if (= "00" ((-> business-message-definitions b-type :errors) ret-code))
      ["00" (if (-> mdef b-type m-type :recv-data)
              (str (:data addi-fields) ((-> mdef b-type m-type :recv-data) recv-data)) (:data addi-fields))]
      (let [e-code ((-> mdef b-type :errors) ret-code)]
        [(if e-code e-code "E1") nil]))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defn business-process
  [fields]
  (let [addi-fields (parse-additional (:additional-data-private fields))
        [ret-code add-data] (business-behavior fields addi-fields)]
    (prn [ret-code add-data])
    (if (= "00" ret-code)
      (do
        (if (= "010001" add-data)
          (spit (str "file/" (:settlement-date fields)) fields :append true))
        (assoc fields :response-code "00" :additional-data-private (write-additional (assoc addi-fields :data add-data)))
      )
      (assoc fields :response-code ret-code))))
